/*
 * graph2D
 * Copyright (c) 2009 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "scene.h"
#include "abort.h"
#include "desktop.h"
#include "graphicDevice.h"
#include "messageWindow.h"
#include "textWindow.h"

namespace Graph2D
{
	Scene::Scene(const unsigned int sceneType)
		: state(0)
		, messageWindow(NULL)
		, textWindow(NULL)
		, dialog(NULL)
		, fadeColor(0)
		, fadeDeltaColor(0)
		, fadeTargetColor(0)
		, fadeColorTime(0)
		, fadeColorEnable(false)
		, particleManager(NULL)
		, scriptProgram(NULL)
		, script(NULL)
	{
		setType(TYPE_SCENE);
		this->sceneType = sceneType;

		setOpaque(true);
		setSize(GraphicDevice::getScreenSize());
	}

	Scene::~Scene()
	{
		unloadScript();

		if(script)
		{
			mana_destroy(script);
			script = NULL;
		}

		if(messageWindow)
		{
//			messageWindow->release();
		}

		if(dialog)
		{
//			dialog->release();
		}
#if 0
		if(particleManager)
		{
			particleManager->release();
		}
#endif
	}

	void Scene::setState(const unsigned int state)
	{
		this->state = state;
	}

	MessageWindow* Scene::getMessageWindow()
	{
		if(!messageWindow)
		{
			const Vector2& size = GraphicDevice::getScreenSize();
			messageWindow = new MessageWindow();
			messageWindow->setPosition(0, size.y - 120);
			messageWindow->setSize(size.x, 120);
		}
		return messageWindow;
	}

	TextWindow* Scene::getTextWindow()
	{
		if(!textWindow)
		{
			const Vector2& size = GraphicDevice::getScreenSize();
			textWindow = new TextWindow();
			textWindow->setPosition(0, size.y - 120);
			textWindow->setSize(size.x, 120);
		}
		return textWindow;
	}

	void Scene::setDialog(Container* dialog, const bool centering)
	{
		this->dialog = dialog;
		if(dialog)
		{
			this->dialog->onUpdateLayout();

			if(centering)
			{
				const Vector2 halfWindowSize = this->dialog->getSize() * 0.5f;
				const Vector2 halfScreenSize = GraphicDevice::getScreenSize() * 0.5f;
				this->dialog->setPosition(halfScreenSize - halfWindowSize);
			}

			this->dialog->open();

			fade(0.8f);
		}
		else
		{
			fade(0.0f);
		}
	}

	ParticleManager* Scene::getParticleManager()
	{
		if(particleManager == NULL)
			particleManager = new ParticleManager();
		return particleManager;
	}

	float Scene::fadein(const float second) const
	{
		Desktop& desktop = Desktop::getInstance();
		desktop.setFadeColor(Graph2D::Color::CLEAR, second);
		return second;
	}

	float Scene::fadeout(const float second) const
	{
		Desktop& desktop = Desktop::getInstance();
		desktop.setFadeColor(Graph2D::Color::BLACK, second);
		return second;
	}

	void Scene::fade(const float alpha, const float second)
	{
		fadeTargetColor = alpha;
		fadeDeltaColor = (alpha - fadeColor) * (1.0f / second);
		fadeColorTime = second;
		fadeColorEnable = true;
	}

	bool Scene::loadScript(const std::string& filename)
	{
		unloadScript();

		scriptProgram = new Data(filename, true);
		if(scriptProgram == NULL)
			return false;
		if(scriptProgram->getBuffer() == NULL)
			return false;

		if(!mana_load_program(getScript(), const_cast<void*>(scriptProgram->getBuffer()), true))
			return false;

		scriptProgramFileName = filename;

		return true;
	}

	void Scene::unloadScript()
	{
		mana_unload_program(getScript());

		scriptProgramFileName.clear();

		delete scriptProgram;
		scriptProgram = NULL;
	}

	mana* Scene::getScript()
	{
		if(script == NULL)
			script = mana_create();
		return script;
	}

	void Scene::onSerialize(mana_stream* stream) const
	{
		super::onSerialize(stream);

		mana_stream_push_unsigned_integer(stream, sceneType);
		mana_stream_push_unsigned_integer(stream, state);

		if(!scriptProgramFileName.empty())
		{
			mana_stream_push_unsigned_char(stream, 1);
			mana_stream_push_string(stream, const_cast<char*>(scriptProgramFileName.c_str()));
		}else{
			mana_stream_push_unsigned_char(stream, 0);
		}
		if(script)
		{
			mana_stream_push_unsigned_char(stream, 1);
			mana_serialize(script, stream);
		}else{
			mana_stream_push_unsigned_char(stream, 0);
		}
		mana_stream_mark(stream);
	}

	void Scene::onDeserialize(mana_stream* stream)
	{
		super::onDeserialize(stream);

		sceneType = mana_stream_pop_unsigned_integer(stream);
		state = mana_stream_pop_unsigned_integer(stream);

		if(mana_stream_pop_unsigned_char(stream))
		{
			// @todo: error check!
			loadScript(std::string(mana_stream_get_string_pointer(stream)));

			mana_steram_seek(stream, mana_stream_get_string_length(stream) + 1);
		}
		if(mana_stream_pop_unsigned_char(stream))
		{
			mana_deserialize(getScript(), stream);
		}
		mana_stream_check(stream);
	}

	void Scene::onDestroy(Component* component)
	{
		onFinalize();

		super::onDestroy(component);
	}

	void Scene::onUpdate(const UpdateInfomation& updateInfomation)
	{
		if(fadeColorEnable)
		{
			fadeColor += fadeDeltaColor* updateInfomation.deltaTime;

			fadeColorTime -= updateInfomation.deltaTime;
			if(fadeColorTime <= 0)
			{
				fadeColor = fadeTargetColor;
				fadeColorEnable = false;
			}
		}

		if(dialog)
		{
			dialog->onUpdateLayout();

			// ダイアログを更新
			dialog->onUpdate(updateInfomation);

			// ダイアログが閉じていたら解放
			if(dialog->closed())
				setDialog(NULL, false);
		}
		else
		{
			if(particleManager)
				particleManager->onUpdate(updateInfomation);

			// スクリプトの更新
			if(script)
			{
				mana_run(script, updateInfomation.deltaTime);
			}

			super::onUpdate(updateInfomation);
		}
	}

	bool Scene::onDraw(const DrawRect& drawRect)
	{
		const Vector2 half = drawRect.getDrawSize() * 0.5f;

		DrawRect newDrawRect(drawRect);
		newDrawRect.position = drawRect.getDrawLeftTopPosition() + half - half * drawRect.getScale();
		newDrawRect.color = drawRect.getDrawColor(getColor());
		newDrawRect.updateClippingInfomation(drawRect);

		super::onDraw(newDrawRect);

		if(fadeColor > 0.0f)
		{
			const Color color(0, 0, 0, fadeColor);
			GraphicDevice::begin(0, GraphicDevice::TRIANGLES, color);
			GraphicDevice::addVertex(Vector2(0.0f, 0.0f));
			GraphicDevice::addVertex(Vector2(GraphicDevice::getScreenWidth(), 0.0f));
			GraphicDevice::addVertex(Vector2(0.0f, GraphicDevice::getScreenHeight()));
			GraphicDevice::addVertex(Vector2(GraphicDevice::getScreenWidth(), 0.0f));
			GraphicDevice::addVertex(Vector2(0.0f, GraphicDevice::getScreenHeight()));
			GraphicDevice::addVertex(GraphicDevice::getScreenSize());
			GraphicDevice::end();
		}

		if(dialog)
		{
			drawChild(newDrawRect, dialog);
		}

		return true;
	}

	bool Scene::touchesBegan(const Vector2& localPosition)
	{
		return dialog ? dialog->touchesBegan(localPosition - dialog->getPosition()) : super::touchesBegan(localPosition);
	}

	bool Scene::touchesMoved(const Vector2& localPosition)
	{
		return dialog ? dialog->touchesMoved(localPosition - dialog->getPosition()) : super::touchesMoved(localPosition);
	}

	bool Scene::touchesEnded(const Vector2& localPosition)
	{
		return dialog ? dialog->touchesEnded(localPosition - dialog->getPosition()) : super::touchesEnded(localPosition);
	}

	bool Scene::touchesCancelled(const Vector2& localPosition)
	{
		return dialog ? dialog->touchesCancelled(localPosition - dialog->getPosition()) : super::touchesCancelled(localPosition);
	}

	bool Scene::compare(const Scene& other) const
	{
		if(!super::compare(other))
			return false;

		if(sceneType != other.sceneType)
			return false;
		if(state != other.state)
			return false;
		if(dialog && dialog->compare(*other.dialog) == false)
			return false;
		//ParticleManager* particleManager;

		return true;
	}
}
